// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System.Net;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Netbios
{
    internal abstract class NameServicePacket
    {
        internal const int Query = 0;

        internal const int Wack = 7;

        internal const int FmtErr = 0x1;

        internal const int SrvErr = 0x2;

        internal const int ImpErr = 0x4;

        internal const int RfsErr = 0x5;

        internal const int ActErr = 0x6;

        internal const int CftErr = 0x7;

        internal const int NbIn = 0x00200001;

        internal const int NbstatIn = 0x00210001;

        internal const int Nb = 0x0020;

        internal const int Nbstat = 0x0021;

        internal const int In = 0x0001;

        internal const int A = 0x0001;

        internal const int Ns = 0x0002;

        internal const int Null = 0x000a;

        internal const int HeaderLength = 12;

        internal const int OpcodeOffset = 2;

        internal const int QuestionOffset = 4;

        internal const int AnswerOffset = 6;

        internal const int AuthorityOffset = 8;

        internal const int AdditionalOffset = 10;

        // opcode
        // rcode
        // type/class
        // header field offsets
        internal static void WriteInt2(int val, byte[] dst, int dstIndex)
        {
            dst[dstIndex++] = unchecked((byte)((val >> 8) & unchecked(0xFF)));
            dst[dstIndex] = unchecked((byte)(val & unchecked(0xFF)));
        }

        internal static void WriteInt4(int val, byte[] dst, int dstIndex)
        {
            dst[dstIndex++] = unchecked((byte)((val >> 24) & unchecked(0xFF)));
            dst[dstIndex++] = unchecked((byte)((val >> 16) & unchecked(0xFF)));
            dst[dstIndex++] = unchecked((byte)((val >> 8) & unchecked(0xFF)));
            dst[dstIndex] = unchecked((byte)(val & unchecked(0xFF)));
        }

        internal static int ReadInt2(byte[] src, int srcIndex)
        {
            return ((src[srcIndex] & unchecked(0xFF)) << 8) + (src[srcIndex + 1] & unchecked(
                0xFF));
        }

        internal static int ReadInt4(byte[] src, int srcIndex)
        {
            return ((src[srcIndex] & unchecked(0xFF)) << 24)
                    + ((src[srcIndex + 1] & unchecked(0xFF)) << 16)
                    + ((src[srcIndex + 2] & unchecked(0xFF)) << 8)
                    + (src[srcIndex + 3] & unchecked(0xFF));
        }

        internal static int ReadNameTrnId(byte[] src, int srcIndex)
        {
            return ReadInt2(src, srcIndex);
        }

        internal int AddrIndex;

        internal NbtAddress[] AddrEntry;

        internal int NameTrnId;

        internal int OpCode;

        internal int ResultCode;

        internal int QuestionCount;

        internal int AnswerCount;

        internal int AuthorityCount;

        internal int AdditionalCount;

        internal bool Received;

        internal bool IsResponse;

        internal bool IsAuthAnswer;

        internal bool IsTruncated;

        internal bool IsRecurDesired;

        internal bool IsRecurAvailable;

        internal bool IsBroadcast;

        internal Name QuestionName;

        internal Name RecordName;

        internal int QuestionType;

        internal int QuestionClass;

        internal int RecordType;

        internal int RecordClass;

        internal int Ttl;

        internal int RDataLength;

        internal IPAddress Addr;

        public NameServicePacket()
        {
            IsRecurDesired = true;
            IsBroadcast = true;
            QuestionCount = 1;
            QuestionClass = In;
        }

        internal virtual int WriteWireFormat(byte[] dst, int dstIndex)
        {
            int start = dstIndex;
            dstIndex += WriteHeaderWireFormat(dst, dstIndex);
            dstIndex += WriteBodyWireFormat(dst, dstIndex);
            return dstIndex - start;
        }

        internal virtual int ReadWireFormat(byte[] src, int srcIndex)
        {
            int start = srcIndex;
            srcIndex += ReadHeaderWireFormat(src, srcIndex);
            srcIndex += ReadBodyWireFormat(src, srcIndex);
            return srcIndex - start;
        }

        internal virtual int WriteHeaderWireFormat(byte[] dst, int dstIndex)
        {
            int start = dstIndex;
            WriteInt2(NameTrnId, dst, dstIndex);
            dst[dstIndex + OpcodeOffset] = unchecked(
                (byte)(
                    (IsResponse
                        ? unchecked(0x80)
                        : unchecked(0x00))
                    + ((OpCode << 3) & unchecked(0x78))
                    + (IsAuthAnswer
                        ? unchecked(0x04)
                        : unchecked(0x00))
                    + (IsTruncated
                        ? unchecked(0x02)
                        : unchecked(0x00))
                    + (IsRecurDesired
                        ? unchecked(0x01)
                        : unchecked(0x00))
                )
            );
            dst[dstIndex + OpcodeOffset + 1] = unchecked(
                (byte)(
                    (IsRecurAvailable
                        ? unchecked(0x80)
                        : unchecked(0x00))
                    + (IsBroadcast
                        ? unchecked(0x10)
                        : unchecked(0x00))
                    + (ResultCode & unchecked(0x0F))
                )
            );
            WriteInt2(QuestionCount, dst, start + QuestionOffset);
            WriteInt2(AnswerCount, dst, start + AnswerOffset);
            WriteInt2(AuthorityCount, dst, start + AuthorityOffset);
            WriteInt2(AdditionalCount, dst, start + AdditionalOffset);
            return HeaderLength;
        }

        internal virtual int ReadHeaderWireFormat(byte[] src, int srcIndex)
        {
            NameTrnId = ReadInt2(src, srcIndex);

            IsResponse = ((src[srcIndex + OpcodeOffset] & unchecked(0x80)) == 0)
                            ? false
                            : true;
            OpCode = (src[srcIndex + OpcodeOffset] & unchecked(0x78)) >> 3;
            IsAuthAnswer = ((src[srcIndex + OpcodeOffset] & unchecked(0x04)) == 0)
                            ? false
                            : true;
            IsTruncated = ((src[srcIndex + OpcodeOffset] & unchecked(0x02)) == 0)
                            ? false
                            : true;
            IsRecurDesired = ((src[srcIndex + OpcodeOffset] & unchecked(0x01)) == 0)
                            ? false
                            : true;
            IsRecurAvailable = ((src[srcIndex + OpcodeOffset + 1] & unchecked(0x80)) == 0)
                            ? false
                            : true;
            IsBroadcast = ((src[srcIndex + OpcodeOffset + 1] & unchecked(0x10)) == 0)
                            ? false
                            : true;
            ResultCode = src[srcIndex + OpcodeOffset + 1] & unchecked(0x0F);
            QuestionCount = ReadInt2(src, srcIndex + QuestionOffset);
            AnswerCount = ReadInt2(src, srcIndex + AnswerOffset);
            AuthorityCount = ReadInt2(src, srcIndex + AuthorityOffset);
            AdditionalCount = ReadInt2(src, srcIndex + AdditionalOffset);
            return HeaderLength;
        }

        internal virtual int WriteQuestionSectionWireFormat(byte[] dst, int dstIndex)
        {
            int start = dstIndex;
            dstIndex += QuestionName.WriteWireFormat(dst, dstIndex);
            WriteInt2(QuestionType, dst, dstIndex);
            dstIndex += 2;
            WriteInt2(QuestionClass, dst, dstIndex);
            dstIndex += 2;
            return dstIndex - start;
        }

        internal virtual int ReadQuestionSectionWireFormat(byte[] src, int srcIndex)
        {
            int start = srcIndex;
            srcIndex += QuestionName.ReadWireFormat(src, srcIndex);
            QuestionType = ReadInt2(src, srcIndex);
            srcIndex += 2;
            QuestionClass = ReadInt2(src, srcIndex);
            srcIndex += 2;
            return srcIndex - start;
        }

        internal virtual int WriteResourceRecordWireFormat(byte[] dst, int dstIndex)
        {
            int start = dstIndex;
            if (RecordName == QuestionName)
            {
                dst[dstIndex++] = unchecked(unchecked(0xC0));
                // label string pointer to
                dst[dstIndex++] = unchecked(unchecked(0x0C));
            }
            else
            {
                // questionName (offset 12)
                dstIndex += RecordName.WriteWireFormat(dst, dstIndex);
            }
            WriteInt2(RecordType, dst, dstIndex);
            dstIndex += 2;
            WriteInt2(RecordClass, dst, dstIndex);
            dstIndex += 2;
            WriteInt4(Ttl, dst, dstIndex);
            dstIndex += 4;
            RDataLength = WriteRDataWireFormat(dst, dstIndex + 2);
            WriteInt2(RDataLength, dst, dstIndex);
            dstIndex += 2 + RDataLength;
            return dstIndex - start;
        }

        internal virtual int ReadResourceRecordWireFormat(byte[] src, int srcIndex)
        {
            int start = srcIndex;
            int end;
            if ((src[srcIndex] & unchecked(0xC0)) == unchecked(0xC0))
            {
                RecordName = QuestionName;
                // label string pointer to questionName
                srcIndex += 2;
            }
            else
            {
                srcIndex += RecordName.ReadWireFormat(src, srcIndex);
            }
            RecordType = ReadInt2(src, srcIndex);
            srcIndex += 2;
            RecordClass = ReadInt2(src, srcIndex);
            srcIndex += 2;
            Ttl = ReadInt4(src, srcIndex);
            srcIndex += 4;
            RDataLength = ReadInt2(src, srcIndex);
            srcIndex += 2;
            AddrEntry = new NbtAddress[RDataLength / 6];
            end = srcIndex + RDataLength;
            for (AddrIndex = 0; srcIndex < end; AddrIndex++)
            {
                srcIndex += ReadRDataWireFormat(src, srcIndex);
            }
            return srcIndex - start;
        }

        internal abstract int WriteBodyWireFormat(byte[] dst, int dstIndex);

        internal abstract int ReadBodyWireFormat(byte[] src, int srcIndex);

        internal abstract int WriteRDataWireFormat(byte[] dst, int dstIndex);

        internal abstract int ReadRDataWireFormat(byte[] src, int srcIndex);

        public override string ToString()
        {
            string opCodeString;
            string resultCodeString;
            string questionTypeString;
            string recordTypeString;

            switch (OpCode)
            {
                case Query:
                    {
                        opCodeString = "QUERY";
                        break;
                    }

                case Wack:
                    {
                        opCodeString = "WACK";
                        break;
                    }

                default:
                    {
                        opCodeString = Extensions.ToString(OpCode);
                        break;
                    }
            }
            switch (ResultCode)
            {
                case FmtErr:
                    {
                        resultCodeString = "FMT_ERR";
                        break;
                    }

                case SrvErr:
                    {
                        resultCodeString = "SRV_ERR";
                        break;
                    }

                case ImpErr:
                    {
                        resultCodeString = "IMP_ERR";
                        break;
                    }

                case RfsErr:
                    {
                        resultCodeString = "RFS_ERR";
                        break;
                    }

                case ActErr:
                    {
                        resultCodeString = "ACT_ERR";
                        break;
                    }

                case CftErr:
                    {
                        resultCodeString = "CFT_ERR";
                        break;
                    }

                default:
                    {
                        resultCodeString = "0x" + Hexdump.ToHexString(ResultCode, 1);
                        break;
                    }
            }
            switch (QuestionType)
            {
                case Nb:
                    {
                        questionTypeString = "NB";
                        break;
                    }

                case Nbstat:
                    {
                        questionTypeString = "NBSTAT";
                        break;
                    }

                default:
                    {
                        questionTypeString = "0x" + Hexdump.ToHexString(QuestionType, 4);
                        break;
                    }
            }
            switch (RecordType)
            {
                case A:
                    {
                        recordTypeString = "A";
                        break;
                    }

                case Ns:
                    {
                        recordTypeString = "NS";
                        break;
                    }

                case Null:
                    {
                        recordTypeString = "NULL";
                        break;
                    }

                case Nb:
                    {
                        recordTypeString = "NB";
                        break;
                    }

                case Nbstat:
                    {
                        recordTypeString = "NBSTAT";
                        break;
                    }

                default:
                    {
                        recordTypeString = "0x" + Hexdump.ToHexString(RecordType, 4);
                        break;
                    }
            }
            return "nameTrnId=" + NameTrnId
                    + ",isResponse=" + IsResponse
                    + ",opCode=" + opCodeString
                    + ",isAuthAnswer=" + IsAuthAnswer
                    + ",isTruncated=" + IsTruncated
                    + ",isRecurAvailable=" + IsRecurAvailable
                    + ",isRecurDesired=" + IsRecurDesired
                    + ",isBroadcast=" + IsBroadcast
                    + ",resultCode=" + ResultCode
                    + ",questionCount=" + QuestionCount
                    + ",answerCount=" + AnswerCount
                    + ",authorityCount=" + AuthorityCount
                    + ",additionalCount=" + AdditionalCount
                    + ",questionName=" + QuestionName
                    + ",questionType=" + questionTypeString
                    + ",questionClass=" + (QuestionClass == In
                                            ? "IN"
                                            : "0x" + Hexdump.ToHexString(QuestionClass, 4))
                    + ",recordName=" + RecordName
                    + ",recordType=" + recordTypeString
                    + ",recordClass=" + (RecordClass == In
                                            ? "IN"
                                            : "0x" + Hexdump.ToHexString(RecordClass, 4))
                    + ",ttl=" + Ttl
                    + ",rDataLength=" + RDataLength;
        }
    }
}
